(function ($) {
  'use strict';

  var SliderCaptcha = function (element, options) {
      this.$element = $(element);
      this.options = $.extend({}, SliderCaptcha.DEFAULTS, options);
      this.$element.css({ 'position': 'relative', 'width': this.options.width + 'px', 'margin': '0 auto' });
      this.init();
  };

  SliderCaptcha.VERSION = '1.0';
  SliderCaptcha.Author = 'argo@163.com';
  SliderCaptcha.DEFAULTS = {
      width: 280,     // canvas宽度
      height: 155,    // canvas高度
      PI: Math.PI,
      sliderL: 42,    // 滑块边长
      sliderR: 9,     // 滑块半径
      offset: 5,      // 容错偏差
      loadingText: '正在加载中...',
      failedText: '再试一次',
      barText: '向右滑动填充拼图',
      repeatIcon: 'fa fa-repeat',
      maxLoadCount: 3,
      localImages: function () {
          return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
      },
      verify: function (arr, url) {
          var ret = false;
          $.ajax({
              url: url,
              data: JSON.stringify(arr),
              async: false,
              cache: false,
              type: 'POST',
              contentType: 'application/json',
              dataType: 'json',
              success: function (result) {
                  ret = result;
              }
          });
          return ret;
      },
      remoteUrl: null
  };

  function Plugin(option) {
      return this.each(function () {
          var $this = $(this);
          var data = $this.data('lgb.SliderCaptcha');
          var options = typeof option === 'object' && option;

          if (data && !/reset/.test(option)) return;
          if (!data) $this.data('lgb.SliderCaptcha', data = new SliderCaptcha(this, options));
          if (typeof option === 'string') data[option]();
      });
  }

  $.fn.sliderCaptcha = Plugin;
  $.fn.sliderCaptcha.Constructor = SliderCaptcha;

  var _proto = SliderCaptcha.prototype;
  _proto.init = function () {
      this.initDOM();
      this.initImg();
      this.bindEvents();
  };

  _proto.initDOM = function () {
      var createElement = function (tagName, className) {
          var elment = document.createElement(tagName);
          elment.className = className;
          return elment;
      };

      var createCanvas = function (width, height) {
          var canvas = document.createElement('canvas');
          canvas.width = parseInt(width/1.1);
          canvas.height = height;
          return canvas;
      };

      var canvas = createCanvas(this.options.width - 2, this.options.height); // 画布
      var block = canvas.cloneNode(true); // 滑块
      var sliderContainer = createElement('div', 'sliderContainer');
      var refreshIcon = createElement('i', 'refreshIcon ' + this.options.repeatIcon);
      var sliderMask = createElement('div', 'sliderMask');
      var sliderbg = createElement('div', 'sliderbg');
      var slider = createElement('div', 'slider');
      var sliderIcon = createElement('i', 'fa fa-arrow-right sliderIcon');
      var text = createElement('span', 'sliderText');

      block.className = 'slider-block';
      text.innerHTML = this.options.barText;

      var el = this.$element;
      el.append($(canvas));
      el.append($(refreshIcon));
      el.append($(block));
      slider.appendChild(sliderIcon);
      sliderMask.appendChild(slider);
      sliderContainer.appendChild(sliderbg);
      sliderContainer.appendChild(sliderMask);
      sliderContainer.appendChild(text);
      el.append($(sliderContainer));

      var _canvas = {
          canvas: canvas,
          block: block,
          sliderContainer: $(sliderContainer),
          refreshIcon: refreshIcon,
          slider: slider,
          sliderMask: sliderMask,
          sliderIcon: sliderIcon,
          text: $(text),
          canvasCtx: canvas.getContext('2d'),
          blockCtx: block.getContext('2d')
      };

      if ($.isFunction(Object.assign)) {
          Object.assign(this, _canvas);
      }
      else {
          $.extend(this, _canvas);
      }
  };

  _proto.initImg = function () {
      var that = this;
      var isIE = window.navigator.userAgent.indexOf('Trident') > -1;
      var L = this.options.sliderL + this.options.sliderR * 2 + 3; // 滑块实际边长
      var drawImg = function (ctx, operation) {
          var l = that.options.sliderL;
          var r = that.options.sliderR;
          var PI = that.options.PI;
          var x = that.x;
          var y = that.y;
          ctx.beginPath();
          ctx.moveTo(x, y);
          ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
          ctx.lineTo(x + l, y);
          ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
          ctx.lineTo(x + l, y + l);
          ctx.lineTo(x, y + l);
          ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
          ctx.lineTo(x, y);
          ctx.lineWidth = 2;
          ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
          ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
          ctx.stroke();
          ctx[operation]();
          ctx.globalCompositeOperation = isIE ? 'xor' : 'destination-over';
      };

      var getRandomNumberByRange = function (start, end) {
          return Math.round(Math.random() * (end - start) + start);
      };
      var img = new Image();
      img.crossOrigin = "Anonymous";
      var loadCount = 0;
      img.onload = function () {
          // 随机创建滑块的位置
          that.x = getRandomNumberByRange(L + 10, that.options.width - (L + 10));
          that.y = getRandomNumberByRange(10 + that.options.sliderR * 2, that.options.height - (L + 10));
          drawImg(that.canvasCtx, 'fill');
          drawImg(that.blockCtx, 'clip');

          that.canvasCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
          that.blockCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
          var y = that.y - that.options.sliderR * 2 - 1;
          var ImageData = that.blockCtx.getImageData(that.x - 3, y, L, L);
          that.block.width = L;
          that.blockCtx.putImageData(ImageData, 0, y + 1);
          that.text.text(that.text.attr('data-text'));
      };
      img.onerror = function () {
          loadCount++;
          if (window.location.protocol === 'file:') {
              loadCount = that.options.maxLoadCount;
              console.error("can't load pic resource file from File protocal. Please try http or https");
          }
          if (loadCount >= that.options.maxLoadCount) {
              that.text.text('加载失败').addClass('text-danger');
              return;
          }
          img.src = that.options.localImages();
      };
      img.setSrc = function () {
          var src = '';
          loadCount = 0;
          that.text.removeClass('text-danger');
          if ($.isFunction(that.options.setSrc)) src = that.options.setSrc();
          if (!src || src === '') src = 'https://picsum.photos/' + that.options.width + '/' + that.options.height + '/?image=' + Math.round(Math.random() * 20);
          if (isIE) { // IE浏览器无法通过img.crossOrigin跨域，使用ajax获取图片blob然后转为dataURL显示
              var xhr = new XMLHttpRequest();
              xhr.onloadend = function (e) {
                  var file = new FileReader(); // FileReader仅支持IE10+
                  file.readAsDataURL(e.target.response);
                  file.onloadend = function (e) {
                      img.src = e.target.result;
                  };
              };
              xhr.open('GET', src);
              xhr.responseType = 'blob';
              xhr.send();
          } else img.src = src;
      };
      img.setSrc();
      this.text.attr('data-text', this.options.barText);
      this.text.text(this.options.loadingText);
      this.img = img;
  };

  _proto.clean = function () {
      this.canvasCtx.clearRect(0, 0, this.options.width, this.options.height);
      this.blockCtx.clearRect(0, 0, this.options.width, this.options.height);
      this.block.width = this.options.width;
  };

  _proto.bindEvents = function () {
      var that = this;
      this.$element.on('selectstart', function () {
          return false;
      });

      $(this.refreshIcon).on('click', function () {
          that.text.text(that.options.barText);
          that.reset();
          if ($.isFunction(that.options.onRefresh)) that.options.onRefresh.call(that.$element);
      });

      var originX, originY, trail = [],
          isMouseDown = false;

      var handleDragStart = function (e) {
          if (that.text.hasClass('text-danger')) return;
          originX = e.clientX || e.touches[0].clientX;
          originY = e.clientY || e.touches[0].clientY;
          isMouseDown = true;
      };

      var handleDragMove = function (e) {
          if (!isMouseDown) return false;
          var eventX = e.clientX || e.touches[0].clientX;
          var eventY = e.clientY || e.touches[0].clientY;
          var moveX = eventX - originX;
          var moveY = eventY - originY;
          if (moveX < 0 || moveX + 40 > that.options.width) return false;
          that.slider.style.left = (moveX - 1) + 'px';
          var blockLeft = (that.options.width - 40 - 20) / (that.options.width - 40) * moveX;
          that.block.style.left = blockLeft + 'px';

          that.sliderContainer.addClass('sliderContainer_active');
          that.sliderMask.style.width = (moveX + 4) + 'px';
          trail.push(Math.round(moveY));
      };

      var handleDragEnd = function (e) {
          if (!isMouseDown) return false;
          isMouseDown = false;
          var eventX = e.clientX || e.changedTouches[0].clientX;
          if (eventX === originX) return false;
          that.sliderContainer.removeClass('sliderContainer_active');
          that.trail = trail;
          var data = that.verify();
          data.verified = true
          if (data.spliced && data.verified) {
              that.sliderContainer.addClass('sliderContainer_success');
              if ($.isFunction(that.options.onSuccess)) that.options.onSuccess.call(that.$element);
          } else {
              that.sliderContainer.addClass('sliderContainer_fail');
              if ($.isFunction(that.options.onFail)) that.options.onFail.call(that.$element);
              setTimeout(function () {
                  that.text.text(that.options.failedText);
                  that.reset();
              }, 1000);
          }
      };

      this.slider.addEventListener('mousedown', handleDragStart);
      this.slider.addEventListener('touchstart', handleDragStart);
      document.addEventListener('mousemove', handleDragMove);
      document.addEventListener('touchmove', handleDragMove);
      document.addEventListener('mouseup', handleDragEnd);
      document.addEventListener('touchend', handleDragEnd);

      document.addEventListener('mousedown', function () { return false; });
      document.addEventListener('touchstart', function () { return false; });
      document.addEventListener('swipe', function () { return false; });
  };

  _proto.verify = function () {
      var arr = this.trail; // 拖动时y轴的移动距离
      var left = parseInt(this.block.style.left);
      var verified = false;
      if (this.options.remoteUrl !== null) {
          verified = this.options.verify(arr, this.options.remoteUrl);
      }
      else {
          var sum = function (x, y) { return x + y; };
          var square = function (x) { return x * x; };
          var average = arr.reduce(sum) / arr.length;
          var deviations = arr.map(function (x) { return x - average; });
          var stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
          verified = stddev !== 0;
      }
      return {
          spliced: Math.abs(left - this.x) < this.options.offset,
          verified: verified
      };
  };

  _proto.reset = function () {
      this.sliderContainer.removeClass('sliderContainer_fail sliderContainer_success');
      this.slider.style.left = 0;
      this.block.style.left = 0;
      this.sliderMask.style.width = 0;
      this.clean();
      this.text.attr('data-text', this.text.text());
      this.text.text(this.options.loadingText);
      this.img.setSrc();
  };
})(jQuery);